(function () {

    var defs = {}; // id -> {dependencies, definition, instance (possibly undefined)}

// Used when there is no 'main' module.
// The name is probably (hopefully) unique so minification removes for releases.
    var register_3795 = function (id) {
        var module = dem(id);
        var fragments = id.split('.');
        var target = Function('return this;')();
        for (var i = 0; i < fragments.length - 1; ++i) {
            if (target[fragments[i]] === undefined)
                target[fragments[i]] = {};
            target = target[fragments[i]];
        }
        target[fragments[fragments.length - 1]] = module;
    };

    var instantiate = function (id) {
        var actual = defs[id];
        var dependencies = actual.deps;
        var definition = actual.defn;
        var len = dependencies.length;
        var instances = new Array(len);
        for (var i = 0; i < len; ++i)
            instances[i] = dem(dependencies[i]);
        var defResult = definition.apply(null, instances);
        if (defResult === undefined)
            throw 'module [' + id + '] returned undefined';
        actual.instance = defResult;
    };

    var def = function (id, dependencies, definition) {
        if (typeof id !== 'string')
            throw 'module id must be a string';
        else if (dependencies === undefined)
            throw 'no dependencies for ' + id;
        else if (definition === undefined)
            throw 'no definition function for ' + id;
        defs[id] = {
            deps: dependencies,
            defn: definition,
            instance: undefined
        };
    };

    var dem = function (id) {
        var actual = defs[id];
        if (actual === undefined)
            throw 'module [' + id + '] was undefined';
        else if (actual.instance === undefined)
            instantiate(id);
        return actual.instance;
    };

    var req = function (ids, callback) {
        var len = ids.length;
        var instances = new Array(len);
        for (var i = 0; i < len; ++i)
            instances.push(dem(ids[i]));
        callback.apply(null, callback);
    };

    var ephox = {};

    ephox.bolt = {
        module: {
            api: {
                define: def,
                require: req,
                demand: dem
            }
        }
    };

    var define = def;
    var require = req;
    var demand = dem;
// this helps with minificiation when using a lot of global references
    var defineGlobal = function (id, ref) {
        define(id, [], function () {
            return ref;
        });
    };
    /*jsc
    ["tinymce.plugins.visualchars.Plugin","tinymce.core.PluginManager","tinymce.core.util.Delay","ephox.katamari.api.Arr","ephox.sugar.api.node.Element","tinymce.plugins.visualchars.core.VisualChars","global!tinymce.util.Tools.resolve","ephox.katamari.api.Option","global!Array","global!Error","global!String","ephox.katamari.api.Fun","global!console","global!document","tinymce.plugins.visualchars.core.Data","tinymce.plugins.visualchars.core.Nodes","ephox.sugar.api.node.Node","global!Object","ephox.sugar.api.node.NodeTypes","tinymce.plugins.visualchars.core.Html"]
    jsc*/
    defineGlobal("global!tinymce.util.Tools.resolve", tinymce.util.Tools.resolve);
    /**
     * ResolveGlobal.js
     *
     * Released under LGPL License.
     * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
     *
     * License: http://www.tinymce.com/license
     * Contributing: http://www.tinymce.com/contributing
     */

    define(
        'tinymce.core.PluginManager',
        [
            'global!tinymce.util.Tools.resolve'
        ],
        function (resolve) {
            return resolve('tinymce.PluginManager');
        }
    );

    /**
     * ResolveGlobal.js
     *
     * Released under LGPL License.
     * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
     *
     * License: http://www.tinymce.com/license
     * Contributing: http://www.tinymce.com/contributing
     */

    define(
        'tinymce.core.util.Delay',
        [
            'global!tinymce.util.Tools.resolve'
        ],
        function (resolve) {
            return resolve('tinymce.util.Delay');
        }
    );

    defineGlobal("global!Array", Array);
    defineGlobal("global!Error", Error);
    define(
        'ephox.katamari.api.Fun',

        [
            'global!Array',
            'global!Error'
        ],

        function (Array, Error) {

            var noop = function () {
            };

            var compose = function (fa, fb) {
                return function () {
                    return fa(fb.apply(null, arguments));
                };
            };

            var constant = function (value) {
                return function () {
                    return value;
                };
            };

            var identity = function (x) {
                return x;
            };

            var tripleEquals = function (a, b) {
                return a === b;
            };

            // Don't use array slice(arguments), makes the whole function unoptimisable on Chrome
            var curry = function (f) {
                // equivalent to arguments.slice(1)
                // starting at 1 because 0 is the f, makes things tricky.
                // Pay attention to what variable is where, and the -1 magic.
                // thankfully, we have tests for this.
                var args = new Array(arguments.length - 1);
                for (var i = 1; i < arguments.length; i++) args[i - 1] = arguments[i];

                return function () {
                    var newArgs = new Array(arguments.length);
                    for (var j = 0; j < newArgs.length; j++) newArgs[j] = arguments[j];

                    var all = args.concat(newArgs);
                    return f.apply(null, all);
                };
            };

            var not = function (f) {
                return function () {
                    return !f.apply(null, arguments);
                };
            };

            var die = function (msg) {
                return function () {
                    throw new Error(msg);
                };
            };

            var apply = function (f) {
                return f();
            };

            var call = function (f) {
                f();
            };

            var never = constant(false);
            var always = constant(true);


            return {
                noop: noop,
                compose: compose,
                constant: constant,
                identity: identity,
                tripleEquals: tripleEquals,
                curry: curry,
                not: not,
                die: die,
                apply: apply,
                call: call,
                never: never,
                always: always
            };
        }
    );

    defineGlobal("global!Object", Object);
    define(
        'ephox.katamari.api.Option',

        [
            'ephox.katamari.api.Fun',
            'global!Object'
        ],

        function (Fun, Object) {

            var never = Fun.never;
            var always = Fun.always;

            /**
             Option objects support the following methods:

             fold :: this Option a -> ((() -> b, a -> b)) -> Option b

             is :: this Option a -> a -> Boolean

             isSome :: this Option a -> () -> Boolean

             isNone :: this Option a -> () -> Boolean

             getOr :: this Option a -> a -> a

             getOrThunk :: this Option a -> (() -> a) -> a

             getOrDie :: this Option a -> String -> a

             or :: this Option a -> Option a -> Option a
             - if some: return self
             - if none: return opt

             orThunk :: this Option a -> (() -> Option a) -> Option a
             - Same as "or", but uses a thunk instead of a value

             map :: this Option a -> (a -> b) -> Option b
             - "fmap" operation on the Option Functor.
             - same as 'each'

             ap :: this Option a -> Option (a -> b) -> Option b
             - "apply" operation on the Option Apply/Applicative.
             - Equivalent to <*> in Haskell/PureScript.

             each :: this Option a -> (a -> b) -> Option b
             - same as 'map'

             bind :: this Option a -> (a -> Option b) -> Option b
             - "bind"/"flatMap" operation on the Option Bind/Monad.
             - Equivalent to >>= in Haskell/PureScript; flatMap in Scala.

             flatten :: {this Option (Option a))} -> () -> Option a
             - "flatten"/"join" operation on the Option Monad.

             exists :: this Option a -> (a -> Boolean) -> Boolean

             forall :: this Option a -> (a -> Boolean) -> Boolean

             filter :: this Option a -> (a -> Boolean) -> Option a

             equals :: this Option a -> Option a -> Boolean

             equals_ :: this Option a -> (Option a, a -> Boolean) -> Boolean

             toArray :: this Option a -> () -> [a]

             */

            var none = function () {
                return NONE;
            };

            var NONE = (function () {
                var eq = function (o) {
                    return o.isNone();
                };

                // inlined from peanut, maybe a micro-optimisation?
                var call = function (thunk) {
                    return thunk();
                };
                var id = function (n) {
                    return n;
                };
                var noop = function () {
                };

                var me = {
                    fold: function (n, s) {
                        return n();
                    },
                    is: never,
                    isSome: never,
                    isNone: always,
                    getOr: id,
                    getOrThunk: call,
                    getOrDie: function (msg) {
                        throw new Error(msg || 'error: getOrDie called on none.');
                    },
                    or: id,
                    orThunk: call,
                    map: none,
                    ap: none,
                    each: noop,
                    bind: none,
                    flatten: none,
                    exists: never,
                    forall: always,
                    filter: none,
                    equals: eq,
                    equals_: eq,
                    toArray: function () {
                        return [];
                    },
                    toString: Fun.constant("none()")
                };
                if (Object.freeze) Object.freeze(me);
                return me;
            })();


            /** some :: a -> Option a */
            var some = function (a) {

                // inlined from peanut, maybe a micro-optimisation?
                var constant_a = function () {
                    return a;
                };

                var self = function () {
                    // can't Fun.constant this one
                    return me;
                };

                var map = function (f) {
                    return some(f(a));
                };

                var bind = function (f) {
                    return f(a);
                };

                var me = {
                    fold: function (n, s) {
                        return s(a);
                    },
                    is: function (v) {
                        return a === v;
                    },
                    isSome: always,
                    isNone: never,
                    getOr: constant_a,
                    getOrThunk: constant_a,
                    getOrDie: constant_a,
                    or: self,
                    orThunk: self,
                    map: map,
                    ap: function (optfab) {
                        return optfab.fold(none, function (fab) {
                            return some(fab(a));
                        });
                    },
                    each: function (f) {
                        f(a);
                    },
                    bind: bind,
                    flatten: constant_a,
                    exists: bind,
                    forall: bind,
                    filter: function (f) {
                        return f(a) ? me : NONE;
                    },
                    equals: function (o) {
                        return o.is(a);
                    },
                    equals_: function (o, elementEq) {
                        return o.fold(
                            never,
                            function (b) {
                                return elementEq(a, b);
                            }
                        );
                    },
                    toArray: function () {
                        return [a];
                    },
                    toString: function () {
                        return 'some(' + a + ')';
                    }
                };
                return me;
            };

            /** from :: undefined|null|a -> Option a */
            var from = function (value) {
                return value === null || value === undefined ? NONE : some(value);
            };

            return {
                some: some,
                none: none,
                from: from
            };
        }
    );

    defineGlobal("global!String", String);
    define(
        'ephox.katamari.api.Arr',

        [
            'ephox.katamari.api.Option',
            'global!Array',
            'global!Error',
            'global!String'
        ],

        function (Option, Array, Error, String) {
            // Use the native Array.indexOf if it is available (IE9+) otherwise fall back to manual iteration
            // https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf
            var rawIndexOf = (function () {
                var pIndexOf = Array.prototype.indexOf;

                var fastIndex = function (xs, x) {
                    return pIndexOf.call(xs, x);
                };

                var slowIndex = function (xs, x) {
                    return slowIndexOf(xs, x);
                };

                return pIndexOf === undefined ? slowIndex : fastIndex;
            })();

            var indexOf = function (xs, x) {
                // The rawIndexOf method does not wrap up in an option. This is for performance reasons.
                var r = rawIndexOf(xs, x);
                return r === -1 ? Option.none() : Option.some(r);
            };

            var contains = function (xs, x) {
                return rawIndexOf(xs, x) > -1;
            };

            // Using findIndex is likely less optimal in Chrome (dynamic return type instead of bool)
            // but if we need that micro-optimisation we can inline it later.
            var exists = function (xs, pred) {
                return findIndex(xs, pred).isSome();
            };

            var range = function (num, f) {
                var r = [];
                for (var i = 0; i < num; i++) {
                    r.push(f(i));
                }
                return r;
            };

            // It's a total micro optimisation, but these do make some difference.
            // Particularly for browsers other than Chrome.
            // - length caching
            // http://jsperf.com/browser-diet-jquery-each-vs-for-loop/69
            // - not using push
            // http://jsperf.com/array-direct-assignment-vs-push/2

            var chunk = function (array, size) {
                var r = [];
                for (var i = 0; i < array.length; i += size) {
                    var s = array.slice(i, i + size);
                    r.push(s);
                }
                return r;
            };

            var map = function (xs, f) {
                // pre-allocating array size when it's guaranteed to be known
                // http://jsperf.com/push-allocated-vs-dynamic/22
                var len = xs.length;
                var r = new Array(len);
                for (var i = 0; i < len; i++) {
                    var x = xs[i];
                    r[i] = f(x, i, xs);
                }
                return r;
            };

            // Unwound implementing other functions in terms of each.
            // The code size is roughly the same, and it should allow for better optimisation.
            var each = function (xs, f) {
                for (var i = 0, len = xs.length; i < len; i++) {
                    var x = xs[i];
                    f(x, i, xs);
                }
            };

            var eachr = function (xs, f) {
                for (var i = xs.length - 1; i >= 0; i--) {
                    var x = xs[i];
                    f(x, i, xs);
                }
            };

            var partition = function (xs, pred) {
                var pass = [];
                var fail = [];
                for (var i = 0, len = xs.length; i < len; i++) {
                    var x = xs[i];
                    var arr = pred(x, i, xs) ? pass : fail;
                    arr.push(x);
                }
                return {pass: pass, fail: fail};
            };

            var filter = function (xs, pred) {
                var r = [];
                for (var i = 0, len = xs.length; i < len; i++) {
                    var x = xs[i];
                    if (pred(x, i, xs)) {
                        r.push(x);
                    }
                }
                return r;
            };

            /*
             * Groups an array into contiguous arrays of like elements. Whether an element is like or not depends on f.
             *
             * f is a function that derives a value from an element - e.g. true or false, or a string.
             * Elements are like if this function generates the same value for them (according to ===).
             *
             *
             * Order of the elements is preserved. Arr.flatten() on the result will return the original list, as with Haskell groupBy function.
             *  For a good explanation, see the group function (which is a special case of groupBy)
             *  http://hackage.haskell.org/package/base-4.7.0.0/docs/Data-List.html#v:group
             */
            var groupBy = function (xs, f) {
                if (xs.length === 0) {
                    return [];
                } else {
                    var wasType = f(xs[0]); // initial case for matching
                    var r = [];
                    var group = [];

                    for (var i = 0, len = xs.length; i < len; i++) {
                        var x = xs[i];
                        var type = f(x);
                        if (type !== wasType) {
                            r.push(group);
                            group = [];
                        }
                        wasType = type;
                        group.push(x);
                    }
                    if (group.length !== 0) {
                        r.push(group);
                    }
                    return r;
                }
            };

            var foldr = function (xs, f, acc) {
                eachr(xs, function (x) {
                    acc = f(acc, x);
                });
                return acc;
            };

            var foldl = function (xs, f, acc) {
                each(xs, function (x) {
                    acc = f(acc, x);
                });
                return acc;
            };

            var find = function (xs, pred) {
                for (var i = 0, len = xs.length; i < len; i++) {
                    var x = xs[i];
                    if (pred(x, i, xs)) {
                        return Option.some(x);
                    }
                }
                return Option.none();
            };

            var findIndex = function (xs, pred) {
                for (var i = 0, len = xs.length; i < len; i++) {
                    var x = xs[i];
                    if (pred(x, i, xs)) {
                        return Option.some(i);
                    }
                }

                return Option.none();
            };

            var slowIndexOf = function (xs, x) {
                for (var i = 0, len = xs.length; i < len; ++i) {
                    if (xs[i] === x) {
                        return i;
                    }
                }

                return -1;
            };

            var push = Array.prototype.push;
            var flatten = function (xs) {
                // Note, this is possible because push supports multiple arguments:
                // http://jsperf.com/concat-push/6
                // Note that in the past, concat() would silently work (very slowly) for array-like objects.
                // With this change it will throw an error.
                var r = [];
                for (var i = 0, len = xs.length; i < len; ++i) {
                    // Ensure that each value is an array itself
                    if (!Array.prototype.isPrototypeOf(xs[i])) throw new Error('Arr.flatten item ' + i + ' was not an array, input: ' + xs);
                    push.apply(r, xs[i]);
                }
                return r;
            };

            var bind = function (xs, f) {
                var output = map(xs, f);
                return flatten(output);
            };

            var forall = function (xs, pred) {
                for (var i = 0, len = xs.length; i < len; ++i) {
                    var x = xs[i];
                    if (pred(x, i, xs) !== true) {
                        return false;
                    }
                }
                return true;
            };

            var equal = function (a1, a2) {
                return a1.length === a2.length && forall(a1, function (x, i) {
                    return x === a2[i];
                });
            };

            var slice = Array.prototype.slice;
            var reverse = function (xs) {
                var r = slice.call(xs, 0);
                r.reverse();
                return r;
            };

            var difference = function (a1, a2) {
                return filter(a1, function (x) {
                    return !contains(a2, x);
                });
            };

            var mapToObject = function (xs, f) {
                var r = {};
                for (var i = 0, len = xs.length; i < len; i++) {
                    var x = xs[i];
                    r[String(x)] = f(x, i);
                }
                return r;
            };

            var pure = function (x) {
                return [x];
            };

            var sort = function (xs, comparator) {
                var copy = slice.call(xs, 0);
                copy.sort(comparator);
                return copy;
            };

            return {
                map: map,
                each: each,
                eachr: eachr,
                partition: partition,
                filter: filter,
                groupBy: groupBy,
                indexOf: indexOf,
                foldr: foldr,
                foldl: foldl,
                find: find,
                findIndex: findIndex,
                flatten: flatten,
                bind: bind,
                forall: forall,
                exists: exists,
                contains: contains,
                equal: equal,
                reverse: reverse,
                chunk: chunk,
                difference: difference,
                mapToObject: mapToObject,
                pure: pure,
                sort: sort,
                range: range
            };
        }
    );
    define("global!console", [], function () {
        if (typeof console === "undefined") console = {
            log: function () {
            }
        };
        return console;
    });
    defineGlobal("global!document", document);
    define(
        'ephox.sugar.api.node.Element',

        [
            'ephox.katamari.api.Fun',
            'global!Error',
            'global!console',
            'global!document'
        ],

        function (Fun, Error, console, document) {
            var fromHtml = function (html, scope) {
                var doc = scope || document;
                var div = doc.createElement('div');
                div.innerHTML = html;
                if (!div.hasChildNodes() || div.childNodes.length > 1) {
                    console.error('HTML does not have a single root node', html);
                    throw 'HTML must have a single root node';
                }
                return fromDom(div.childNodes[0]);
            };

            var fromTag = function (tag, scope) {
                var doc = scope || document;
                var node = doc.createElement(tag);
                return fromDom(node);
            };

            var fromText = function (text, scope) {
                var doc = scope || document;
                var node = doc.createTextNode(text);
                return fromDom(node);
            };

            var fromDom = function (node) {
                if (node === null || node === undefined) throw new Error('Node cannot be null or undefined');
                return {
                    dom: Fun.constant(node)
                };
            };

            return {
                fromHtml: fromHtml,
                fromTag: fromTag,
                fromText: fromText,
                fromDom: fromDom
            };
        }
    );

    /**
     * Plugin.js
     *
     * Released under LGPL License.
     * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
     *
     * License: http://www.tinymce.com/license
     * Contributing: http://www.tinymce.com/contributing
     */
    define(
        'tinymce.plugins.visualchars.core.Data',

        [],

        function () {
            var charMap = {
                '\u00a0': 'nbsp',
                '\u00ad': 'shy'
            };

            var charMapToRegExp = function (charMap, global) {
                var key, regExp = '';

                for (key in charMap) {
                    regExp += key;
                }

                return new RegExp('[' + regExp + ']', global ? 'g' : '');
            };

            var charMapToSelector = function (charMap) {
                var key, selector = '';

                for (key in charMap) {
                    if (selector) {
                        selector += ',';
                    }
                    selector += 'span.mce-' + charMap[key];
                }

                return selector;
            };

            return {
                charMap: charMap,
                regExp: charMapToRegExp(charMap),
                regExpGlobal: charMapToRegExp(charMap, true),
                selector: charMapToSelector(charMap),
                charMapToRegExp: charMapToRegExp,
                charMapToSelector: charMapToSelector
            };
        }
    );
    define(
        'ephox.sugar.api.node.NodeTypes',

        [],

        function () {
            return {
                ATTRIBUTE: 2,
                CDATA_SECTION: 4,
                COMMENT: 8,
                DOCUMENT: 9,
                DOCUMENT_TYPE: 10,
                DOCUMENT_FRAGMENT: 11,
                ELEMENT: 1,
                TEXT: 3,
                PROCESSING_INSTRUCTION: 7,
                ENTITY_REFERENCE: 5,
                ENTITY: 6,
                NOTATION: 12
            };
        }
    );
    define(
        'ephox.sugar.api.node.Node',

        [
            'ephox.sugar.api.node.NodeTypes'
        ],

        function (NodeTypes) {
            var name = function (element) {
                var r = element.dom().nodeName;
                return r.toLowerCase();
            };

            var type = function (element) {
                return element.dom().nodeType;
            };

            var value = function (element) {
                return element.dom().nodeValue;
            };

            var isType = function (t) {
                return function (element) {
                    return type(element) === t;
                };
            };

            var isComment = function (element) {
                return type(element) === NodeTypes.COMMENT || name(element) === '#comment';
            };

            var isElement = isType(NodeTypes.ELEMENT);
            var isText = isType(NodeTypes.TEXT);
            var isDocument = isType(NodeTypes.DOCUMENT);

            return {
                name: name,
                type: type,
                value: value,
                isElement: isElement,
                isText: isText,
                isDocument: isDocument,
                isComment: isComment
            };
        }
    );

    define(
        'tinymce.plugins.visualchars.core.Html',
        [
            'tinymce.plugins.visualchars.core.Data'
        ],
        function (Data) {
            var wrapCharWithSpan = function (value) {
                return '<span data-mce-bogus="1" class="mce-' + Data.charMap[value] + '">' + value + '</span>';
            };

            return {
                wrapCharWithSpan: wrapCharWithSpan
            };
        }
    );

    /**
     * Plugin.js
     *
     * Released under LGPL License.
     * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
     *
     * License: http://www.tinymce.com/license
     * Contributing: http://www.tinymce.com/contributing
     */

    define(
        'tinymce.plugins.visualchars.core.Nodes',

        [
            'ephox.katamari.api.Arr',
            'ephox.sugar.api.node.Element',
            'ephox.sugar.api.node.Node',
            'tinymce.plugins.visualchars.core.Data',
            'tinymce.plugins.visualchars.core.Html'
        ],

        function (Arr, Element, Node, Data, Html) {
            var isMatch = function (n) {
                return Node.isText(n) &&
                    Node.value(n) !== undefined &&
                    Data.regExp.test(Node.value(n));
            };

            // inlined sugars PredicateFilter.descendants for file size
            var filterDescendants = function (scope, predicate) {
                var result = [];
                var dom = scope.dom();
                var children = Arr.map(dom.childNodes, Element.fromDom);

                Arr.each(children, function (x) {
                    if (predicate(x)) {
                        result = result.concat([x]);
                    }
                    result = result.concat(filterDescendants(x, predicate));
                });
                return result;
            };

            var findParentElm = function (elm, rootElm) {
                while (elm.parentNode) {
                    if (elm.parentNode === rootElm) {
                        return elm;
                    }
                    elm = elm.parentNode;
                }
            };

            var replaceWithSpans = function (html) {
                return html.replace(Data.regExpGlobal, Html.wrapCharWithSpan);
            };

            return {
                isMatch: isMatch,
                filterDescendants: filterDescendants,
                findParentElm: findParentElm,
                replaceWithSpans: replaceWithSpans
            };
        }
    );
    /**
     * Plugin.js
     *
     * Released under LGPL License.
     * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
     *
     * License: http://www.tinymce.com/license
     * Contributing: http://www.tinymce.com/contributing
     */

    define(
        'tinymce.plugins.visualchars.core.VisualChars',

        [
            'tinymce.plugins.visualchars.core.Data',
            'tinymce.plugins.visualchars.core.Nodes',
            'ephox.katamari.api.Arr',
            'ephox.sugar.api.node.Element',
            'ephox.sugar.api.node.Node'
        ],

        function (Data, Nodes, Arr, Element, Node) {
            var show = function (editor, rootElm) {
                var node, div;
                var nodeList = Nodes.filterDescendants(Element.fromDom(rootElm), Nodes.isMatch);

                Arr.each(nodeList, function (n) {
                    var withSpans = Nodes.replaceWithSpans(Node.value(n));

                    div = editor.dom.create('div', null, withSpans);
                    while ((node = div.lastChild)) {
                        editor.dom.insertAfter(node, n.dom());
                    }

                    editor.dom.remove(n.dom());
                });
            };

            var hide = function (editor, body) {
                var nodeList = editor.dom.select(Data.selector, body);

                Arr.each(nodeList, function (node) {
                    editor.dom.remove(node, 1);
                });
            };

            var toggle = function (editor) {
                var body = editor.getBody();
                var bookmark = editor.selection.getBookmark();
                var parentNode = Nodes.findParentElm(editor.selection.getNode(), body);

                // if user does select all the parentNode will be undefined
                parentNode = parentNode !== undefined ? parentNode : body;

                hide(editor, parentNode);
                show(editor, parentNode);

                editor.selection.moveToBookmark(bookmark);
            };


            return {
                show: show,
                hide: hide,
                toggle: toggle
            };
        }
    );
    /**
     * Plugin.js
     *
     * Released under LGPL License.
     * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
     *
     * License: http://www.tinymce.com/license
     * Contributing: http://www.tinymce.com/contributing
     */
    define(
        'tinymce.plugins.visualchars.Plugin',
        [
            'tinymce.core.PluginManager',
            'tinymce.core.util.Delay',
            'ephox.katamari.api.Arr',
            'ephox.sugar.api.node.Element',
            'tinymce.plugins.visualchars.core.VisualChars'
        ],
        function (PluginManager, Delay, Arr, Element, VisualChars) {
            PluginManager.add('visualchars', function (editor) {
                var self = this, state;

                var toggleActiveState = function () {
                    var self = this;

                    editor.on('VisualChars', function (e) {
                        self.active(e.state);
                    });
                };

                var debouncedToggle = Delay.debounce(function () {
                    VisualChars.toggle(editor);
                }, 300);

                if (editor.settings.forced_root_block !== false) {
                    editor.on('keydown', function (e) {
                        if (self.state === true) {
                            e.keyCode === 13 ? VisualChars.toggle(editor) : debouncedToggle();
                        }
                    });
                }

                editor.addCommand('mceVisualChars', function () {
                    var body = editor.getBody(), selection = editor.selection, bookmark;

                    state = !state;
                    self.state = state;
                    editor.fire('VisualChars', {state: state});

                    bookmark = selection.getBookmark();

                    if (state === true) {
                        VisualChars.show(editor, body);
                    } else {
                        VisualChars.hide(editor, body);
                    }

                    selection.moveToBookmark(bookmark);
                });

                editor.addButton('visualchars', {
                    title: 'Show invisible characters',
                    cmd: 'mceVisualChars',
                    onPostRender: toggleActiveState
                });

                editor.addMenuItem('visualchars', {
                    text: 'Show invisible characters',
                    cmd: 'mceVisualChars',
                    onPostRender: toggleActiveState,
                    selectable: true,
                    context: 'view',
                    prependToContext: true
                });
            });

            return function () {
            };
        }
    );
    dem('tinymce.plugins.visualchars.Plugin')();
})();
